import { DataSource } from '@angular/cdk/table';
import { BehaviorSubject, Subject, combineLatest } from 'rxjs';
import { map, debounceTime, tap } from 'rxjs/operators';
import { setFlatTreeMap, searchExpanded } from './utils';
import { TreeFilterConfig, TreeFilterConfig as FilterConfig } from './interface';

export class TreeDataSource<T> extends DataSource<T> {
  // 是否为异步，异步时认为data永远是经过条件处理的
  async: boolean;

  // 平铺的源数据
  flatDataMap: Map<any, T>;

  // 过滤条件清除之后是否保留展开的能力
  keepExpanded: boolean = true;

  // 设置比较的key
  compareWith: string | ((o: any) => any);

  // 源数据
  private readonly _data$: BehaviorSubject<T[]>;

  // renderData流，依赖data变更
  private readonly _renderData$ = new Subject<T[]>();

  // 过滤条件
  private readonly _filter$: BehaviorSubject<FilterConfig> = new BehaviorSubject(null);

  private _filterState$: BehaviorSubject<Map<string, FilterConfig>> = new BehaviorSubject(null);

  constructor(initialData: any[], async: boolean, compareWith: string | ((o: any) => any), keepExpanded: boolean) {
    super();
    const data = typeof initialData === 'undefined' ? [] : initialData;
    this.async = typeof async === 'undefined' ? false : async;
    this.compareWith = compareWith;
    this.keepExpanded = keepExpanded;
    this._data$ = new BehaviorSubject(data);
    this._updateChangeSubscription();
  }

  // 源数据
  get data(): T[] {
    return this._data$.value;
  }

  get filter(): TreeFilterConfig {
    return this._filter$.value;
  }

  set data(data: T[]) {
    this._data$.next(data);
  }

  // 过滤
  set filter(data: FilterConfig) {
    this._filter$.next(data);
  }

  // NEED_DO
  disconnect(): void {}

  connect(): Subject<any> {
    return this._renderData$;
  }

  // filter state
  setFilterState(data: Map<string, FilterConfig>): void {
    this._filterState$.next(data);
  }

  getFilterState(): BehaviorSubject<Map<string, TreeFilterConfig>> {
    return this._filterState$;
  }

  // tslint:disable-next-line
  _filterData(data: T[], filter: FilterConfig): T[] {
    searchExpanded(data as any, filter, this.keepExpanded);
    return data;
  }

  // tslint:disable-next-line
  _filterStateData(data: T[], stateMap: Map<string, FilterConfig>): T[] {
    let _res = data || [];
    if (stateMap && data.length) {
      stateMap.forEach((item): T[] => (_res = this._filterData(_res, item)));
    }
    return _res;
  }

  _updateChangeSubscription(): void {
    const dataStream = this._data$;
    const filterStateStream = this.getFilterState();

    combineLatest([dataStream, filterStateStream])
      .pipe(
        debounceTime(1),
        tap(([data]): void => setFlatTreeMap(data as any, this.compareWith)),
        map(([data, filter]): any => {
          if (this.async) {
            return data;
          }
          return this._filterStateData(data, filter);
        }),
      )
      .subscribe((res): void => {
        this._renderData$.next(res);
      });
  }
}
